﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace ExecutionContract
{
    public class ExecutionContractWriter : IExecutionContractWriter
    {
        #region Properties
        /// <summary>
        /// This is the element that should contain the xml code of the executioncontracts
        /// </summary>
        public XmlElement Root
        {
            get
            {
                return this._root;
            }
            set
            {
                this._root = value;
                this.Document = this.Root.OwnerDocument;
            }
        }

        private XmlDocument Document { get; set; }

        #endregion

        #region Private Fields and Const

        private XmlElement _root;
        private const string STANDARD_ENCLOSING_TAGNAME = "CodeInjection";

        #endregion
        #region Constructors
        /// <summary>
        /// This is the empty constructor of an ExecutionContractWriter
        /// </summary>
        public ExecutionContractWriter()
        {

        }
        /// <summary>
        /// This is the standard contructor of an ExecutionContractWriter. It assigns the root element after it gets called.
        /// </summary>
        /// <param name="_root">This is the element that should contain the xml code of the executioncontracts.</param>
        public ExecutionContractWriter(XmlElement _root)
        {
            this.Root = _root;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// This method writes xml code into the root element. 
        /// </summary>
        /// <param name="ListToWrite">List of ExecutionContracts that should be written into the xml.</param>
        public void Write(List<ExecutionContract> ListToWrite)
        {
            //TODO Check if EnclosingTag is already in the document
            XmlElement EnclosingTag = Document.CreateElement(STANDARD_ENCLOSING_TAGNAME);

            XmlElement CurrentTag = null;
            XmlElement PropertiesTag = null;
            XmlElement PropertyTag = null;
            XmlAttribute ExecutionContractAttributeValue = null;
            XmlElement MembersTag = null;
            XmlAttribute PropAttribute = null;
            foreach (ExecutionContract CurrentExecutionContract in ListToWrite)
            {
                CurrentTag = Document.CreateElement("ExecutionContract");
                //Create the attribute that contains the name of the type
                ExecutionContractAttributeValue = Document.CreateAttribute("Name");
                ExecutionContractAttributeValue.InnerText = CurrentExecutionContract.ContainingType.Name;

                //add the name attribute
                CurrentTag.Attributes.Append(ExecutionContractAttributeValue);

                //create the namespace attribute
                ExecutionContractAttributeValue = Document.CreateAttribute("Namespace");
                ExecutionContractAttributeValue.InnerText = CurrentExecutionContract.ContainingType.Namespace;

                //add the namespace
                CurrentTag.Attributes.Append(ExecutionContractAttributeValue);


                ExecutionContractAttributeValue = Document.CreateAttribute("Assembly");
                ExecutionContractAttributeValue.InnerText = CurrentExecutionContract.ContainingType.Assembly.GetName().Name ;
                CurrentTag.Attributes.Append(ExecutionContractAttributeValue);

                PropertiesTag = Document.CreateElement("Properties");
                foreach (PropertyInfo propInfo in CurrentExecutionContract.ContainingType.GetProperties())
                {
                    PropertyTag = Document.CreateElement("Property");
                    PropertyTag.InnerText = propInfo.PropertyType.ToString();
                    PropAttribute = Document.CreateAttribute("Name");
                    PropAttribute.InnerText = propInfo.Name;

                    PropertyTag.Attributes.Append(PropAttribute);

                    PropertiesTag.AppendChild(PropertyTag);
                }
                CurrentTag.AppendChild(PropertiesTag);
                MembersTag = Document.CreateElement("Members");
                //take care of the members
                foreach (MemberInfo MemberInfo in CurrentExecutionContract.Members)
                {
                    MembersTag.AppendChild(GenerateCodeForMember(MemberInfo));
                }

                CurrentTag.AppendChild(MembersTag);
                //add the tag
                EnclosingTag.AppendChild(CurrentTag);
            }

            //TODO depends on the existence of the element
            Root.AppendChild(EnclosingTag);
        }

        /// <summary>
        /// This method writes xml code into the root element. 
        /// </summary>
        /// <param name="ListToWrite">List of ExecutionContracts that should be written into the xml.</param>
        /// <param name="root">root element that should contain the xml code</param>
        public void Write(List<ExecutionContract> ListToWrite, System.Xml.XmlElement root)
        {
            throw new NotImplementedException();
        }
        #endregion


        #region Private Methods

        private string ByteToString(byte[] ByteArray)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            foreach (byte b in ByteArray)
                sb.Append(Convert.ToString(b, 2).PadLeft(8, '0'));
            return sb.ToString();
        } 
        /// <summary>
        /// This method is a proxy to redirect the special member types to the right membertype. So it is easy to extend the possible members.
        /// </summary>
        /// <param name="MemberInfo">Member that should be written</param>
        /// <returns>Xml code that is generated for the member</returns>
        private XmlElement GenerateCodeForMember(MemberInfo MemberInfo)
        {
            //splitter to call the right method
            switch (MemberInfo.MemberType)
            {
                case MemberTypes.Method: return GenerateCodeForMember((MethodInfo)MemberInfo);
                default: throw new Exception("Unsupported Membertype");
            }

        }

        /// <summary>
        /// This method generates the xml code for ExecutionContractMembers which are a method.
        /// </summary>
        /// <param name="_methodInfo">Member that should be written</param>
        /// <returns>Xml code that is generated for the member</returns>
        private XmlElement GenerateCodeForMember(MethodInfo _methodInfo)
        {
            ExecutionMemberAttribute attr =(ExecutionMemberAttribute) _methodInfo.GetCustomAttributes(typeof(ExecutionMemberAttribute), true).First();
            
            XmlElement ReturnTag = Document.CreateElement("Member");
            //Name
            XmlAttribute memberName = Document.CreateAttribute("Name");
            memberName.InnerText = _methodInfo.Name;

            ReturnTag.Attributes.Append(memberName);

            //Modifier
            memberName = Document.CreateAttribute("Modifier");
            memberName.InnerText = GenerateModifier(_methodInfo);
            ReturnTag.Attributes.Append(memberName);

            //Input
            ReturnTag.AppendChild(GenerateXMLForInput(_methodInfo.GetParameters()));

            

            //return 
            ReturnTag.AppendChild(GenerateXMLForReturn(_methodInfo.ReturnType));

            if (attr.isStreamed)
            {
                //MethodBody                   
                ReturnTag.AppendChild(GenerateXMLForMethodBody(_methodInfo.GetMethodBody()));
            }
            else
            {
                ReturnTag.AppendChild(GenerateXMLForLocalMethodBody(_methodInfo,attr));
            }

            return ReturnTag;
        }

        private string GenerateModifier(MethodInfo MemberInfo)
        {
            string retValue = "";

            if (MemberInfo.IsPublic)
            {
                retValue += "public ";
            }
            if (MemberInfo.IsPrivate)
            {
                retValue += "private ";
            }
            if (MemberInfo.IsStatic)
            {
                retValue += "static ";
            }
            if (MemberInfo.IsFinal)
            {
                retValue += "final ";
            }
          
            if (MemberInfo.IsVirtual)
            {
                retValue += "virtual ";
            }

            return retValue;
        } 

      

        private XmlElement GenerateXMLForInput(ParameterInfo[] Parameters)
        {
            XmlElement InputTag = Document.CreateElement("Input");

            foreach (ParameterInfo Parameter in Parameters)
            {
                InputTag.AppendChild(GenerateXMLForParamter(Parameter));
            }
            return InputTag;
        }


        private XmlNode GenerateXMLForLocalMethodBody(MethodInfo _methodInfo, ExecutionMemberAttribute attr)
        {
            XmlElement InputTag = Document.CreateElement("Body");
            XmlAttribute isLocaleAttribute = Document.CreateAttribute("isLocal");
            isLocaleAttribute.InnerText = "true";
            InputTag.Attributes.Append(isLocaleAttribute);
            ExecutionContractServerSideGenerator generator = new ExecutionContractServerSideGenerator();
            InputTag.InnerText = generator.GenerateStringValue(attr.Path, _methodInfo);
            return InputTag; 
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="methodBody"></param>
        /// <returns></returns>
        private XmlElement GenerateXMLForMethodBody(MethodBody _methodBody)
        {
            XmlElement InputTag = Document.CreateElement("Body");            

            return InputTag;
        }

        private XmlElement GenerateXMLForParamter(ParameterInfo Parameter)
        {
            XmlElement ParameterTag = Document.CreateElement("Parameter");
            ParameterTag.InnerText = Parameter.ParameterType.FullName;

            return ParameterTag;
        }

        private XmlNode GenerateXMLForReturn(Type type)
        {
            XmlElement ReturnTag = Document.CreateElement("Return");

            ReturnTag.InnerText = type.FullName;

            return ReturnTag;
        }
   

        #endregion
    }
}